#region Licence
// Copyright (c) 2008 Tasman Logic Ltd

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
// and associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies 
// or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace ActiveSharp.Inspection
{
    public class Operand
    {
        internal Operand(MethodBase method, OpCode op, byte[] operandBytes)
        {
            // save method and opcode because we need to know them later, when calculating values of some of our properties
            _method = method;
            _op = op;
            RawOperandBytes = operandBytes;
        }

        MethodBase _method;
        OpCode _op;
        public readonly byte[] RawOperandBytes;

        void CheckOpCode(string message, params OpCode[] requiredOpCodes)
        {
            if (Array.IndexOf(requiredOpCodes, _op) < 0)
                throw new InvalidOperationException(message);
        }

        /// <summary>
        /// Returns the operand cast to a method.  Only applicable to operands of call instructions (and newobj 
        /// i.e. constructor calls). This method will throw an exception for all others.
        /// </summary>
        public MethodBase AsMethod
        {
            get
            {
                // calli has a different kind of operand. C# and VB compilers don't emit calli: http://blogs.msdn.com/b/shawnfa/archive/2004/06/14/155478.aspx 
                CheckOpCode("AsMethod is only supported for Call, Callvirt and NewObj opcodes at present.  Calli may be supported later",
                    OpCodes.Call, 
                    OpCodes.Callvirt,
                    OpCodes.Newobj);   // newobj = constructor call

                Int32 msilMethodToken = BitConverter.ToInt32(RawOperandBytes, 0);

                //Re the way this code handles generics: that's by the 2nd and 3rd params to ResolveMethod
                //See http://blogs.msdn.com/b/yirutang/archive/2005/07/05/435556.aspx
                Type declaringType = _method.DeclaringType;
                return declaringType.Module.ResolveMethod(               // works on constructors too
                                      msilMethodToken,
                                      declaringType.GetGenericArguments(),
                                      (_method is ConstructorInfo ? null : _method.GetGenericArguments())   // constructors cant't have generic args in C# (although their containing type might)
                                     );
            }
        }

        /// <summary>
        /// Returns the operand cast to a field.  Only applicable to operands of ldfld(a) instructions, will
        /// throw an exception for all others.
        /// </summary>
        public FieldInfo AsField
        {
            get
            {
                CheckOpCode("AsField is only supported for Ldfld(a) opcodes",
                        OpCodes.Ldfld,
                        OpCodes.Ldflda);

                Int32 msilFieldToken = BitConverter.ToInt32(RawOperandBytes, 0);

                //Re the way this code handles generics: see http://blogs.gotdotnet.com/yirutang/archive/2005/07/05/435556.aspx 
                Type declaringType = _method.DeclaringType;
                return declaringType.Module.ResolveField(
                                    msilFieldToken,
                                    declaringType.GetGenericArguments(),
                                    _method.GetGenericArguments());        //TODO: just out of curiosity, how can this last param actually be necessary/ relevant?

            }
        }

        /// <summary>
        /// Returns the operand case to a string.  Only applicable to operands of Ldstr instructions, will 
        /// throw an exception for all others
        /// </summary>
        public string AsString
        {
            get
            {
                CheckOpCode("AsString is only supported for Ldstr opcode at present", OpCodes.Ldstr);

                Int32 msilStringToken = BitConverter.ToInt32(RawOperandBytes, 0);                
                Type declaringType = _method.DeclaringType;
                return declaringType.Module.ResolveString(msilStringToken);
            }
        }

        /// <summary>
        /// Returns operand as a Type object
        /// </summary>
        public Type AsType
        {
            get
            {
                CheckOpCode("AsType is currently only supported for the contstrained. prefix",
                    OpCodes.Constrained);  // can add more later

                Int32 msilTypeToken = BitConverter.ToInt32(RawOperandBytes, 0);

                Type declaringType = _method.DeclaringType;
                return declaringType.Module.ResolveType(
                                    msilTypeToken,
                                    declaringType.GetGenericArguments(),
                                    _method.GetGenericArguments());

            }
        }
    }
}
